/**************************************************************************
 * Copyright (C) 2012 Tyler Robertson
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/


#include "Arduino.h"
#include "Wifly/WiflyDevice.h"
#include "SPI/SerialPeripheralInterface.h"

// SC16IS740 Register Definitions
#define IER        (0x01 << 3)
#define FCR        (0x02 << 3)
#define IIR        (0x02 << 3)
#define LCR        (0x03 << 3)
#define MCR        (0x04 << 3)
#define LSR        (0x05 << 3)
#define MSR        (0x06 << 3)
#define SPR        (0x07 << 3)
#define TXFIFO     (0x08 << 3)
#define RXFIFO     (0x09 << 3)
#define DLAB       (0x80 << 3)
#define IODIR      (0x0A << 3)
#define IOSTATE    (0x0B << 3)
#define IOINTMSK   (0x0C << 3)
#define IOCTRL     (0x0E << 3)
#define EFCR       (0x0F << 3)

#define DLL        (0x00 << 3)
#define DLM        (0x01 << 3)
#define EFR        (0x02 << 3)
#define XON1       (0x04 << 3)  
#define XON2       (0x05 << 3)
#define XOFF1      (0x06 << 3)
#define XOFF2      (0x07 << 3)

#define BRIDGE_CS_PIN 10

static bool is_in_command_mode = false;
static char txt_buffer[256];


// Functions to read and write to the sc16is740 spi<->uart bridge

//-------------------------------------------------------------------------------------
static void write_to_bridge_register( unsigned char reg, unsigned char value )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( reg );
	spi_write_byte( value );
	spi_deselect_slave( BRIDGE_CS_PIN );
}

//-------------------------------------------------------------------------------------
static unsigned char read_from_bridge_register( unsigned char reg )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( reg | 0x80 );
	unsigned char data =  spi_read_byte();
	spi_deselect_slave( BRIDGE_CS_PIN );
	return data;
}




//-------------------------------------------------------------------------------------
bool wifly_initialize()
{
	Serial.println( "Initializing wifly device..." );
	
	// Set CS Pin as output
	pinMode( BRIDGE_CS_PIN, OUTPUT );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	
	// Initialize the SC16IS740 Bridge
	spi_select_slave( BRIDGE_CS_PIN );
	write_to_bridge_register( LCR, 0x80 ); // Set mode to "enable divisor latch"
	write_to_bridge_register( DLL, 0x50 ); // Program least significant divisor
	write_to_bridge_register( DLM, 0x00 ); // Program most significant divisor
	write_to_bridge_register( LCR, 0xBF );
	write_to_bridge_register( EFR, 0x10 );
	write_to_bridge_register( LCR, 0x03 ); // Set to 8 bit word length
	write_to_bridge_register( FCR, 0x06 ); // Clear and reset TX & RX registers
	write_to_bridge_register( FCR, 0x01 ); // Enable FIFO mode
	
	// Test
	write_to_bridge_register( SPR, 'H' );
	char received_byte = read_from_bridge_register( SPR );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	if( received_byte == 'H' )
	{
		Serial.println( "SPI<->UART bridge initialized!" );
		return true;
	}
	
	
	Serial.println( "Could not initialize SPI<->UART bridge!!" );
	return false;
}

//-------------------------------------------------------------------------------------
bool wifly_join_network( const char *ssid, const char *password )
{	
	if( !wifly_enter_command_mode() )
		return false;

	wifly_send_command( Crlf( "set c r 0" ) );
	
	sprintf( txt_buffer, Crlf( "set w p %s" ), password );
	
	wifly_send_command( txt_buffer, "AOK" );
	
	sprintf( txt_buffer, Crlf( "join %s" ), ssid );
	
	bool succeeded = wifly_send_command( txt_buffer, "Associated!", 10000 );
	
	delay( 1000 );
	
	wifly_exit_command_mode();
	
	return succeeded;
}

//-------------------------------------------------------------------------------------
unsigned char wifly_receive_byte()
{
	return read_from_bridge_register( 0 );
}

//-------------------------------------------------------------------------------------
void wifly_send_byte( unsigned char data )
{
	write_to_bridge_register( 0, data );
}

//-------------------------------------------------------------------------------------
void wifly_send_data( const unsigned char *data, unsigned int size_in_bytes )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );

	// Transmit Holding Register (actually a FIFO) can only hold 64 bytes
	int i=0;
	for( ; i<size_in_bytes/64; ++i )
	{
		spi_write_array( data + i*64, 64 );
		spi_deselect_slave( BRIDGE_CS_PIN );
	}
	
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_array( data + i*64, size_in_bytes%64 );
	spi_deselect_slave( BRIDGE_CS_PIN );	
}

//-------------------------------------------------------------------------------------
void wifly_receive_data( unsigned char *data, unsigned int size_in_bytes )
{
	// Receiving Holding Register can only hold 64 bytes
	int i=0;
	for( ; i<size_in_bytes/64; ++i )
	{
		spi_select_slave( BRIDGE_CS_PIN );
		spi_write_byte( 0 );
		spi_read_array( data + i*64, 64 );
		spi_deselect_slave( BRIDGE_CS_PIN );
	}

	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_read_array( data + i*64, size_in_bytes%64 );
	spi_deselect_slave( BRIDGE_CS_PIN );	
}

//-------------------------------------------------------------------------------------
bool wifly_factory_reset()
{
	if( !wifly_enter_command_mode() )
		return false;
		
	if( !wifly_send_command( Crlf( "factory RESET" ) ) )
		return false;
	
	delay( 1000 );
	
	return wifly_exit_command_mode();
}

//-------------------------------------------------------------------------------------
bool wifly_enter_command_mode()
{
	if( !is_in_command_mode )
	{
		is_in_command_mode = wifly_send_command( "\r\nexit\r\n$$$", "CMD" );
	}
	
	return is_in_command_mode;
}

//-------------------------------------------------------------------------------------
bool wifly_exit_command_mode()
{
	if( is_in_command_mode )
	{
		is_in_command_mode = !wifly_send_command( Crlf( "exit" ), "EXIT" );
	}
	
	return !is_in_command_mode;
}

//-------------------------------------------------------------------------------------
bool wifly_is_in_command_mode()
{
	return is_in_command_mode;
}

//-------------------------------------------------------------------------------------
bool wifly_has_data_available()
{
	return ( read_from_bridge_register( LSR ) & 0x01 != 0 );
}

//-------------------------------------------------------------------------------------
unsigned char wifly_get_num_bytes_available()
{
	return read_from_bridge_register( RXFIFO );
}

//-------------------------------------------------------------------------------------
void wifly_send_string( const char *str )
{
	if( str )
		wifly_send_data( (const unsigned char*)str, strlen( str ) );
}

//-------------------------------------------------------------------------------------
bool wifly_send_command( const char *cmdstring, const char *response, unsigned long timeoutMS )
{
	wifly_send_string( cmdstring );
	
	if( !response )
		return true; // Assume succeeded
		
	char i = 0;
	char c = 0;
	
	unsigned long endTime = millis() + timeoutMS;

	while( millis() < endTime && response[i] != 0 )
	{
        if( wifly_has_data_available() )
        {
			c = wifly_receive_byte();
			
			Serial.print( c );  // echo
			
			if( response[i] == c )
			{
				i++; // match, get ready to test the next char
			}
			else
			{
				i = 0; // no match, start over
				if( response[i] == c )
				{
					i++; // check to see if matches first char after fault
				}
			}
        }
	}
	
	return response[i] == 0;
}

//-------------------------------------------------------------------------------------
bool wifly_open_connection( const char *host, unsigned long port )
{
	if( !wifly_enter_command_mode() )
		return false;
	
	sprintf( txt_buffer, "open %s %d\r\n", host, port );
	
	return wifly_send_command( txt_buffer );
}

//-------------------------------------------------------------------------------------
bool wifly_close_connection()
{
	if( !wifly_enter_command_mode() )
		return false;
	
	return wifly_send_command( Crlf( "close" ) );
}

//-------------------------------------------------------------------------------------
bool wifly_is_associated()
{
	if( !wifly_enter_command_mode() )
		return false;
	
	bool associated = wifly_send_command( Crlf( "show net" ), "Assoc=OK" );
	
	wifly_exit_command_mode();
	
	return associated;
}

//-------------------------------------------------------------------------------------
/* Intented to be called as a last measure for accessing the wifly from the arduino serial port.
 *
 * Use in a loop
 */
void wifly_debug_serial()
{
	static char buff[256];
	static char count = 0;
	
	if (Serial.available() > 0)
	{
		char c = Serial.read();
		
		if( count < 254 )
		{
			buff[ count++ ] = c;
		}
	}
	else
	{
		if( count )
		{

//			for( int i=0; i<count; ++i )
//			{
//				Serial.print( buff[i] );
//				Serial.print( ": (" );
//				Serial.print( buff[ i ], HEX );
//				Serial.print( ")\n" );
//			}
			
			buff[ count ] = 0;
			wifly_send_command( buff );
		}
				
		count = 0;
		
		// The serial out gets all garbled if we don't give it a bit
		delay( 1 );
	}
	
	// Print any output from the wifly
	while( wifly_has_data_available() )
	{
		char c = wifly_receive_byte();
		Serial.print( c );
	}
}